#互相关运算

import torch
from torch import nn
from d2l import torch as d2l

def corr2d(X, K):
    """计算二维互相关运算。"""
    h, w = K.shape
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i:i + h, j:j + w] * K).sum()
    return Y

#验证上述二维互相关运算的输出
X = torch.tensor([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0], [6.0, 7.0, 8.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
print(corr2d(X, K))

#实现二维卷积层
#基于上⾯定义的corr2d函数实现⼆维卷积层。在__init__构造函数中，将weight和bias声明为两个模型
#参数。前向传播函数调⽤corr2d函数并添加偏置
class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        super().__init__()
        self.weight = nn.Parameter(torch.rand(kernel_size))
        self.bias = nn.Parameter(torch.zeros(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias
#⾼度和宽度分别为h和w的卷积核可以被称为h × w卷积或h × w卷积核。我们也将带有h × w卷积核的卷积层
#称为h × w卷积层
#卷积层的一个简单应用： 检测图像中不同颜色的边缘
#如下是卷积层的⼀个简单应⽤：通过找到像素变化的位置，来检测图像中不同颜⾊的边缘。⾸先，我们构造
#⼀个6 × 8像素的⿊⽩图像。中间四列为⿊⾊（0），其余像素为⽩⾊（1）。
X = torch.ones((6, 8))
X[:, 2:6] = 0
print(X)
#接下来，我们构造⼀个⾼度为1、宽度为2的卷积核K。当进⾏互相关运算时，如果⽔平相邻的两元素相同，则
#输出为零，否则输出为⾮零。
#输出Y中的1代表从白色到黑色的边缘，-1代表从黑色到白色的边缘
K = torch.tensor([[1.0, -1.0]])
Y=corr2d(X,K)
print(Y)
#卷积核K只可以检测垂直边缘
#现在我们将输⼊的⼆维图像转置，再进⾏如上的互相关运算。其输出如下，之前检测到的垂直边缘消失了。
#不出所料，这个卷积核K只可以检测垂直边缘，⽆法检测⽔平边缘。
print(corr2d(X.t(), K))

#学习由X生成Y的卷积核
## 构造⼀个⼆维卷积层，它具有1个输出通道和形状为（1，2）的卷积核
conv2d = nn.Conv2d(1, 1, kernel_size=(1, 2), bias=False)#批量数 通道数
# 这个⼆维卷积层使⽤四维输⼊和输出格式（批量⼤⼩、通道、⾼度、宽度），
# 其中批量⼤⼩和通道数都为1
X = X.reshape((1, 1, 6, 8))#批量数 通道数 行 列
Y = Y.reshape((1, 1, 6, 7))

for i in range(10):
    Y_hat = conv2d(X)
    l = (Y_hat - Y)**2
    conv2d.zero_grad()
    l.sum().backward()
    ## 迭代卷积核
    conv2d.weight.data[:] -= 3e-2 * conv2d.weight.grad#梯度下降
    if (i + 1) % 2 == 0:
        print(f'batch {i+1}, loss {l.sum():.3f}')

#所学的卷积核的权重张量
print(conv2d.weight.data.reshape((1, 2)))

